#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/cdev.h>
#include <linux/device.h>

MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("pcie device driver");

#define DEV_NAME "hello_pcie"
#define DEVICE_NUMBER 1
#define DMA_BUFFER_SIZE 1*1024*1024
#define FASYNC_MINOR 1
#define FASYNC_MAJOR 244


static struct class *hello_class;
static struct device* hello_class_dev;

static struct hello_device {
    struct pci_dev* pci_dev;
    struct cdev cdev;
    dev_t devno;
} my_device;

struct bar_info {
    unsigned long phy;
    unsigned long vir;
    unsigned long length;
};

struct bar_info bar0_info;
struct bar_info bar1_info;

struct dma_addr_info_t{
dma_addr_t src_phy;
dma_addr_t src_virt;
dma_addr_t dst_phy;
dma_addr_t dst_virt;
};

struct dma_addr_info_t dma_addr_info;

#define HELLO_VENDER_ID 0x02
#define HELLO_DEVICE_ID 0x05

static struct pci_device_id hello_ids[] = {
    {HELLO_VENDER_ID, HELLO_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
    {0, }
};

MODULE_DEVICE_TABLE(pci, hello_ids);

static int hello_probe(struct pci_dev* pdev, const struct pci_device_id* id);
static void hello_remove(struct pci_dev* pdev);
static irqreturn_t hello_interrupt(int irq, void* dev);

static int hello_open(struct inode* inode, struct file* file)
{
    printk("hello open!\n");
    return 0;
};

static int hello_release(struct inode* inode, struct file* file)
{
    printk("hello release!\n");
    return 0;
};

long hello_unlocked_ioctl(struct file* file, unsigned int cmd, unsigned long arg)
{
    printk("hello unlocked ioctl!\n");
    return 0;
};


void iATU_write_config_dword(struct pci_dev* pdev, int offset, int value)
{
    /* data */
}

static void IATU_bar0(void)
{

}

int dma_read_config_dword(struct pci_dev* pdev, int offset)
{
    int value = 0;
    return value;
}

void dma_write_config_dword(struct pci_dev* pdev, int offset)
{

}

static struct file_operations hello_ops = {
    .owner = THIS_MODULE,
    .open = hello_open,
    .release = hello_release,
    .unlocked_ioctl = hello_unlocked_ioctl,
};

static struct pci_driver hello_driver =
{
   .name = DEV_NAME,
   .id_table = hello_ids,
   .probe = hello_probe,
   .remove = hello_remove
};

void dma_init(void)
{
    int pos;
	u16 msi_control;
	u32 msi_addr_l;
	u32 msi_addr_h;
	u32 msi_data;

	//1.dma 通道0 写初始化 。如何访问DMA global register 寄存器组需要根据具体的硬件，可以通过pci_write/read_config_word/dword，
	//也可以通过某个bar，比如通过bar0+偏移量访问。
	//1.1 DMA write engine enable =0x1，这里请根据自己的芯片填写
	//dma_write_config_dword(->pci_dev,DMA write engine enable,0x1);
	//1.2 获取msi能力寄存器的地址
	pos =pci_find_capability(my_device.pci_dev,PCI_CAP_ID_MSI);
	//1.3 读取msi的协议部分，得到pci设备是32位还是64位，不同的架构msi data寄存器地址同
	pci_read_config_word(my_device.pci_dev,pos+2,&msi_control);
	//1.4 读取msi能力寄存器组中的地址寄存器的值
	pci_read_config_dword(my_device.pci_dev,pos+4,&msi_addr_l);
	//1.5 设置 DMA write done IMWr Address Low.这里请根据自己的芯片填写
	//dma_write_config_dword(my_device.pci_dev,DMA write done IMWr Address Low,msi_addr_l);
	//1.6 设置 DMA write abort IMWr Address Low.这里请根据自己的芯片填写
	//dma_write_config_dword(my_device.pci_dev,DMA write abort IMWr Address Low,msi_addr_l);

	if(msi_control&0x80){
		//64位的
		//1.7 读取msi能力寄存器组中的高32位地址寄存器的值
		pci_read_config_dword(my_device.pci_dev,pos+0x8,&msi_addr_h);
		//1.8 读取msi能力寄存器组中的数据寄存器的值
		pci_read_config_dword(my_device.pci_dev,pos+0xc,&msi_data);

		//1.9 设置 DMA write done IMWr Address High.这里请根据自己的芯片填写
		//dma_write_config_dword(my_device.pci_dev,DMA write done IMWr Address High,msi_addr_h);
		//1.10 设置 DMA write abort IMWr Address High.这里请根据自己的芯片填写
		//dma_write_config_dword(my_device.pci_dev,DMA write abort IMWr Address High,msi_addr_h);

	} else {
		//1.11 读取msi能力寄存器组中的数据寄存器的值
		pci_read_config_dword(my_device.pci_dev,pos+0x8,&msi_data);
	}

	//1.12 把数据寄存器的值写入到dma的控制寄存器组中的 DMA write channel 0 IMWr data中
	//dma_write_config_dword(my_device.pci_dev,DMA write channel 0 IMWr data,msi_data);

	//1.13 DMA channel 0 control register 1 = 0x4000010
	//dma_write_config_dword(my_device.pci_dev,DMA channel 0 control register 1,0x4000010);

	//2.dma 通道0 读初始化 和上述操作类似，不再叙述。
}

static int hello_probe(struct pci_dev* pdev, const struct pci_device_id* id)
{
    int i;
    int result;

    if (pci_enable_device(pdev)) {
        result = -EIO;
        goto end;
    }

    pci_set_master(pdev);
    my_device.pci_dev = pdev;
    if (unlikely(pci_request_regions(pdev, DEV_NAME))) {
        printk("failed to pci_request_regions\n");
        result = -EIO;
        goto enable_device_error;
    }

    bar0_info.phy = pci_resource_start(pdev, 0);
    if (bar0_info.phy < 0) {
        printk("failed to pci_resource_start\n");
        result = -EIO;
        goto request_regios_error;
    }

    bar0_info.length = pci_resource_len(pdev, 0);
    if (bar0_info.length != 0) {
        bar0_info.vir = (unsigned long) ioremap(bar0_info.phy, bar0_info.length);
    }

    dma_addr_info.src_virt = (dma_addr_t) pci_alloc_consistent(pdev, DMA_BUFFER_SIZE, &dma_addr_info.src_phy);//???
    if (dma_addr_info.src_virt != 0) {
        for (i = 0; i < DMA_BUFFER_SIZE/ PAGE_SIZE; i++) {
            SetPageReserved(virt_to_page(dma_addr_info.src_phy + i * PAGE_SIZE));
        }
    } else {
        goto free_bar0;
    }

    dma_addr_info.dst_virt = (dma_addr_t) pci_alloc_consistent(pdev, DMA_BUFFER_SIZE, &dma_addr_info.dst_phy);//???
    if (dma_addr_info.dst_virt != 0) {
        for (i = 0; i < DMA_BUFFER_SIZE/ PAGE_SIZE; i++) {
            SetPageReserved(virt_to_page(dma_addr_info.dst_phy + i * PAGE_SIZE));
        }
    } else {
        goto alloc_src_error;
    }

    result = pci_enable_msi(pdev);
    if (unlikely(result)) {
        printk("failed to pci_enable_msi\n");
        goto alloc_dma_dst_error;
    }

    result = request_irq(pdev->irq, hello_interrupt, 0, DEV_NAME, my_device.pci_dev);
    if (unlikely(result)) {
        printk("failed to request_irq\n");
        goto enable_msi_error;
    }

    dma_init();

enable_msi_error:
    pci_disable_msi(pdev);

alloc_dma_dst_error:
    for(i=0; i<DMA_BUFFER_SIZE/PAGE_SIZE; i++){
		ClearPageReserved(virt_to_page(dma_addr_info.dst_phy+i*PAGE_SIZE));
	}
	pci_free_consistent(pdev, DMA_BUFFER_SIZE, (void *)(dma_addr_info.dst_virt), dma_addr_info.dst_phy);


alloc_src_error:
    for(i=0; i<DMA_BUFFER_SIZE/PAGE_SIZE; i++){
		ClearPageReserved(virt_to_page(dma_addr_info.src_phy+i*PAGE_SIZE));
	}
	pci_free_consistent(pdev, DMA_BUFFER_SIZE, (void *)(dma_addr_info.src_virt), dma_addr_info.src_phy);


free_bar0:
    iounmap((void*)bar0_info.vir);

request_regios_error:
    pci_release_regions(pdev);

enable_device_error:
    pci_disable_device(pdev);

end:
    return result;
}

static void hello_remove(struct pci_dev* pdev)
{
    int i;

    free_irq(pdev->irq, my_device.pci_dev);
    pci_disable_msi(pdev);

    for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
		ClearPageReserved(virt_to_page(dma_addr_info.dst_phy+i*PAGE_SIZE));
	}
	pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_addr_info.dst_virt, dma_addr_info.dst_phy);

	for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
		ClearPageReserved(virt_to_page(dma_addr_info.src_phy+i*PAGE_SIZE));
	}
	pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_addr_info.src_virt,dma_addr_info.src_phy);

	iounmap((void *)bar0_info.vir);
	pci_release_regions(pdev);
	pci_disable_device(pdev);
}

static irqreturn_t hello_interrupt(int irq, void* dev)
{
    return 0;

}

static int hello_drv_init(void)
{
    int ret;
    ret = pci_register_driver(&hello_driver);
    if (ret < 0) {
        printk("failed to pci_register_driver\n");
        return ret;
    }

    ret = alloc_chrdev_region(&my_device.devno, 0, DEVICE_NUMBER, "hello");
    if (ret < 0) {
        printk("failed to register_chardev_region\n");
        return ret;
    }

    cdev_init(&my_device.cdev, &hello_ops);
    ret = cdev_add(&my_device.cdev, my_device.devno, DEVICE_NUMBER);
    if (ret < 0) {
        printk("failed to cdev_add\n");
        return ret;
    }

    //hello_class = class_create(THIS_MODULE, "hello_class");
    //hello_class_dev = device_create(hello_class, NULL, my_device.devno, NULL, "hello_device");

    return 0;
}

static void hello_drv_exit(void)
{
    //device_destory(hello_class, my_device.devno);

    //class_destory(hello_class);

    cdev_del(&(my_device.cdev));
    unregister_chrdev_region(my_device.devno, DEVICE_NUMBER);
    pci_unregister_driver(&hello_driver);
}

module_init(hello_drv_init);
module_exit(hello_drv_exit);